Pre-amble: various functions useful for generating slides¶

For convenience, put slides.html generating code at the top:¶

Imports:

show object:

Generate QR-Code's for Demo and Github:

ImageDisplayer objects:

New in PyTeal: Boxes and ABI-Compatible Smart Contracts¶

image.png

Learn how to utilize unlimited global storage and ABI-compatibility in PyTeal, Algorand’s library for writing smart contracts in Python.¶

Agenda
¶

I. PyTeal¶

II. Application Boxes in PyTeal¶

III. PyTeal's ABI-Router: Building an ARC-4 Application¶

IV. Demo¶

V. Questions¶

References
¶

Decipher 2021: Writing Smart Contracts with Python¶

https://www.github.com/jasonpaulos/decipher-22-pyteal-talk¶

In [6]:
github_qr_code()

image.png

.

PyTeal
¶

image.png

.

image.png

.

image.png

image.png

.

Polling dApp
¶

image.png

Favorite Tall Building?¶

1. Empire State - New York¶

2. Burj Khalifa - Dubai¶

3. Abraj Al-Bait - Mecca¶

4. Taipei 101 - Taipei¶

5. Shanghai Tower - Shanghai¶

6. Merdeka 118 - Kuala Lumpur¶

7. Other¶

image.png

.

Polling dApp
¶

Poll administrator can:¶

create and delete the poll application¶

open and close a poll for voting¶

Anyone can:¶

submit a choice¶

get the poll status¶

image.png

.

Example¶

submit choice Burj Khalifa - Dubai¶

After 100 submissions¶

status =¶

  • 11 chose Empire State - New York
  • 35 chose Burj Khalifa - Dubai
  • 22 chose Abraj Al-Bait - Mecca
  • ...

image.png

.

Voting accounts must contribute exactly +1 to tally¶

image.png

image.png

.

Voting accounts must contribute exactly +1 to tally¶

image.png

image.png

.

Voting accounts must contribute exactly +1 to tally¶

image.png

image.png

.

NOTES:

  • Can't keep all this info in global storage
    • Stats - 8KB total possible. Assuming no compression and 32B per address, that's at most 256 accounts we can keep track of
  • Can keep info in local - but then there are some downsides:
    • voters need to opt into poll (MBR issues)
    • need to have policies and code around closing/clearing out
  • TRICKY Sybil attack. Why can't some create tons of accounts and vote many times. AND it's even easier to pull that off with the reduced MBR requirements thanks to boxes...
    • Answer: yes, this isn't meant to be a solution to the general problem of governance voting, etc.
    • This is only meant for low-stake polls

Voting accounts must contribute exactly +1 to tally¶

global storage solution?¶

No. Unlimited voting accounts but 8 KB max¶

local storage solution?¶

Not really. Want lightweight dApp with no opt-in¶

image.png

.

limits reference¶

https://github.com/algorand/go-algorand/blob/995ae47e80c50e7632034cac8a70b7d6434d03e3/config/consensus.go#L969-L970

64 keys X (64 + 64 bytes) == 8,196 bytes

min MBR = 0.15 Algos with 1 local¶

complex code/policies for opt in, close out, clear state¶

Anything else we could do?¶

Introduce voting token¶

Yes, BUT requires asset opt-in and aiming for lightweight app¶

Notes:

  • expensive 0.1 Algos / acct vs. 0.0157 Algos / acct w/ boxes
  • complicated / error prone
    • need to keep track of asset id off chain and supply it as a foreign ref, then parse out URL, verify that first 32 bytes are the account, etc...
    • need to group with Asset Config Txn which specifies how the URL changes

SOLUTION via Application Boxes
¶

image.png

Notes:

  • In this solution, only need
    • 32 bytes for the Key (max is 64 bytes)
    • 1 byte for the value (max is 32 KB)
  • Costs in terms of MBR formula of 2500 + 400*(len(key) + len(value))
    • 15.7 mili-Algo's to store submission info per account
    • Each txn allows up to 8 box references in it with the size of boxes touched totalling at most 1KB * # of refs
    • In our case, this is not an issue. A single box reference -being the submission account- will suffice

image.png

.

Application Boxes in PyTeal
¶

image.png

.

Notes

  • create: create a box of specified size, with a NoOp if already exists. Returns an indicator for whether box actually got created
  • length: when the box exists, gives its value's size. When it doesn't its hasValue() will be false
  • delete: don't forget to do this as apps don't clean up their own boxes!!! Returns indicator for whether box actually got deleted
  • replace: ... nothing to add ...
  • extract: ... nothing to add ...
  • put and get: the workhorses we'll see in action shortly
  • put: for setting the entire contents of a box, creating it when it doesn't exist. Returns indicator for whether a box was created during execution
  • get: get the entire contents of a box. hasValue() will be false if the box doesn't actually exist

PyTeal App.box_*() API
¶

App.box_create(name, size) - any size ($\leq$ 32KB)¶

App.box_length(name) - opcode box_len¶

App.box_delete(name) - opcode box_del¶

App.box_replace(name, idx, L) - set part of box¶

App.box_extract(name, idx, L) - get part of box¶

App.box_put(name, value) - set value (may create box)¶

App.box_get(name) - get everything (fail if size > 4KB)¶

image.png

.

App.box_get() and App.box_put()¶

Notes

Inspired by

@router.method
def submit(choice: abi.Uint8) -> Expr:
  • sender_box is a MaybeValue:
    • sender_box.has_value() <--> box exists
    • sender_box.value() --> (when box exists) full contents
In [7]:
from pyteal import Int, Itob, Txn, App, Seq, If, Assert, Btoi
# for real app, choice is passed in as parameter.
choice = Itob(Int(1))  # 1: index of "Burj Khalifa - Dubai"

# App.box:    account --> choice
# App.global: choice  --> count
account = Txn.sender()   # box's name = sender's account
old_choice = App.box_get(account) # MaybeValue
submit_expr = Seq( # switch vote old_choice ---> choice
  If(old_choice.hasValue()).Then(
    App.globalPut(      # App.global[old_choice] -= 1 :
      old_choice.value(),
      App.globalGet(old_choice.value()) - Int(1)
    ),
  ),
  App.box_put(account, choice),  # App.box[account] <--- choice
  # App.global[choice] += 1 :
  App.globalPut(choice, App.globalGet(choice) + Int(1)),
)

image.png

.

PyTeal's ABI-Router: Building an ARC-4 Application¶

image.png

.

methods - allow interacting with Poll App¶

* create - called only during app creation and only by administrator¶

* open - called only by administrator¶

* close - called only by administrator¶

* submit¶

* status¶

(didn't include delete for reasons to be explained shortly)¶

image.png

.

Refresher - OnComplete Actions for App Transactions¶

image.png

image.png

.

Value Name Description
0 NoOp Execute ApprovalProgram only
1 OptIn Allocate local state and execute ApprovalProgram
2 CloseOut Execute ApprovalProgram and clear local state
3 ClearState Execute ClearStateProgram and clear locals (even if rejects)
4 UpdateApplication Execute ApprovalProgram and update programs
5 DeleteApplication Execute ApprovalProgram and delete the app

ABI method: code for app transaction with first argument the selector¶

ABI bare app call: app transaction with no arguments. Its action is the code to be executed¶

For Polling dApp:¶

delete is a bare app call for OnComplete=DeleteApplication¶

image.png

.

PyTeal Router and its M.O.E. Questions¶

In [8]:
moe()

image.png

.

The Router delegates to appropriate method or bare app call action based on:¶

[M] In a bare app call? If not, which method selected?¶

[O] Which OnComplete is requested?¶

[E] This app already exists? (Conversely, being created?)¶

image.png

.

Router Compilation into an ABI Smart Contract¶

image.png

image.png

.

Router Initialization¶

In [9]:
warning()
In [10]:
# WARNING: STUBS ARE FOR ROUTER-ILLUSTRATION PURPOSES ONLY!!!
del_action = OnCompleteAction.call_only(Seq()) 

router = Router(
    name="OpenPollingApp",
    descr="This is a polling application.",
    bare_calls=BareCallActions(delete_application=del_action),
)

image.png

.

Add methods with @router.method decorator¶

In [11]:
@router.method
def submit(choice: abi.Uint8) -> Expr:
    """Submit a response to the poll.""" # <-- Docstring
    return Seq()

ABI provides standard for encoding variety of data types¶

abi.Uint8 - An 8-bit unsigned integer encoded as big-endian bit sequence¶

There are more: Uint16/32/64, Bool, Byte, String, StaticArray, DynamicArray, Tuple, ...¶

Link: PyTeal ABI Types
¶

image.png

.

Compile¶

In [12]:
opts = OptimizeOptions(scratch_slots=True)
(approval, clear, json_contract) = \
    router.compile_program(version=8, optimize=opts)
# ------------------------------------^^^^^^^^^^^^^

image.png

.

JSON contract¶

In [13]:
print(json.dumps(json_contract.dictify(), indent=2))
{
  "name": "OpenPollingApp",
  "methods": [
    {
      "name": "submit",
      "args": [
        {
          "type": "uint8",
          "name": "choice"
        }
      ],
      "returns": {
        "type": "void"
      },
      "desc": "Submit a response to the poll."
    }
  ],
  "networks": {},
  "desc": "This is a polling application."
}

image.png

.

Routing Nitty Gritty¶

image.png

.

In [15]:
%%script false --no-raise-error #pragma version 8
txn NumAppArgs
int 0
==
bnz main_l4
txna ApplicationArgs 0
method "submit(uint8)void"
==
bnz main_l3
err
main_l3:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
txna ApplicationArgs 1
int 0
getbyte
callsub submit_0
int 1
return
main_l4:
txn OnCompletion
int DeleteApplication
==
bnz main_l6
err
main_l6:
txn ApplicationID
int 0
!=
assert
int 1
return

// submit
submit_0:
store 0
retsub

Routing method submit(choice: abi.Uint8)¶

In [17]:
show() # approval
txna ApplicationArgs 0
method "submit(uint8)void"
==
bnz main_l3
. . .
main_l3:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
txna ApplicationArgs 1
int 0
getbyte
callsub submit_0
int 1
return
. . .
// submit
submit_0:
store 0
retsub

image.png

.

Routing bare call for delete¶

In [19]:
show() # approval
txn NumAppArgs
int 0
==
bnz main_l4
. . .
main_l4:
txn OnCompletion
int DeleteApplication
==
bnz main_l6
. . .
main_l6:
txn ApplicationID
int 0
!=
assert
int 1
return

image.png

.

Demo: https://jasonpaulos.github.io/decipher-22-pyteal-talk/¶

In [20]:
demo_qr_code()

image.png

.

APPENDIX - The Actual Program¶

image.png

.

NOTES:

  • make sure to put the final version here
In [21]:
# This example is provided for informational purposes only and has not been audited for security.
import json
from typing import Literal
from pyteal import *


pragma(compiler_version="0.20.1")

on_delete = Seq(
    Assert(Txn.sender() == Global.creator_address()),
    InnerTxnBuilder.Execute(
        {
            TxnField.type_enum: TxnType.Payment,
            TxnField.close_remainder_to: Txn.sender(),
        }
    ),
)


router = Router(
    name="OpenPollingApp",
    descr="A polling application with no restrictions on who can participate.",
    bare_calls=BareCallActions(
        delete_application=OnCompleteAction.call_only(on_delete)
    ),
)

NUM_OPTIONS = 7

open_key = Bytes(b"open")
resubmit_key = Bytes(b"resubmit")
question_key = Bytes(b"question")
option_name_prefix = b"option_name_"
option_name_keys = [Bytes(option_name_prefix + bytes([i])) for i in range(NUM_OPTIONS)]
option_count_prefix = b"option_count_"
option_count_keys = [
    Bytes(option_count_prefix + bytes([i])) for i in range(NUM_OPTIONS)
]


@router.method(no_op=CallConfig.CREATE)
def create(
    question: abi.String, options: abi.StaticArray[abi.String, Literal[NUM_OPTIONS]], can_resubmit: abi.Bool  # type: ignore[valid-type]
) -> Expr:
    """Create a new polling application.

    Args:
        question: The question this poll is asking.
        options: A list of options for the poll. This list should not contain duplicate entries.
        can_resubmit: Whether this poll allows accounts to change their submissions or not.
    """
    name = abi.make(abi.String)
    return Seq(
        App.globalPut(open_key, Int(0)),
        App.globalPut(resubmit_key, can_resubmit.get()),
        App.globalPut(question_key, question.get()),
        *[
            Seq(
                name.set(options[i]),
                App.globalPut(option_name_keys[i], name.get()),
                App.globalPut(option_count_keys[i], Int(0)),
            )
            for i in range(NUM_OPTIONS)
        ],
    )


@router.method(name="open")
def open_poll() -> Expr:
    """Marks this poll as open.

    This will fail if the poll is already open.

    The poll must be open in order to receive user input.
    """
    return Seq(
        Assert(Not(App.globalGet(open_key))),
        App.globalPut(open_key, Int(1)),
    )


@router.method(name="close")
def close_poll() -> Expr:
    """Marks this poll as closed.

    This will fail if the poll is already closed.
    """
    return Seq(
        Assert(App.globalGet(open_key)),
        App.globalPut(open_key, Int(0)),
    )


@router.method
def submit(choice: abi.Uint8) -> Expr:
    """Submit a response to the poll.

    Submissions can only be received if the poll is open. If the poll is closed, this will fail.

    If a submission has already been made by the sender and the poll allows resubmissions, the
    sender's choice will be updated to the most recent submission. If the poll does not allow
    resubmissions, this action will fail.

    Args:
        choice: The choice made by the sender. This must be an index into the options for this poll.
    """
    new_choice_count_key = ScratchVar(TealType.bytes)
    old_choice_count_key = ScratchVar(TealType.bytes)
    return Seq(
        Assert(choice.get() < Int(NUM_OPTIONS)),
        new_choice_count_key.store(
            SetByte(option_count_keys[0], Int(len(option_count_prefix)), choice.get())
        ),
        sender_box := App.box_get(Txn.sender()),
        If(sender_box.hasValue()).Then(
            # the sender has already submitted a response, so it must be cleared
            Assert(App.globalGet(resubmit_key)),
            old_choice_count_key.store(
                SetByte(
                    option_count_keys[0],
                    Int(len(option_count_prefix)),
                    Btoi(sender_box.value()),
                )
            ),
            App.globalPut(
                old_choice_count_key.load(),
                App.globalGet(old_choice_count_key.load()) - Int(1),
            ),
        ),
        App.box_put(Txn.sender(), choice.encode()),
        App.globalPut(
            new_choice_count_key.load(),
            App.globalGet(new_choice_count_key.load()) + Int(1),
        ),
    )


class PollStatus(abi.NamedTuple):
    question: abi.Field[abi.String]
    can_resubmit: abi.Field[abi.Bool]
    is_open: abi.Field[abi.Bool]
    results: abi.Field[abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[NUM_OPTIONS]]]  # type: ignore[valid-type]


@router.method
def status(*, output: PollStatus) -> Expr:
    """Get the status of this poll.

    Returns:
        A tuple containing the following information, in order: the question is poll is asking,
        whether the poll allows resubmission, whether the poll is open, and an array of the poll's
        current results. This array contains one entry per option, and each entry is a tuple of that
        option's value and the number of accounts who have voted for it.
    """
    question = abi.make(abi.String)
    can_resubmit = abi.make(abi.Bool)
    is_open = abi.make(abi.Bool)
    option_name = abi.make(abi.String)
    option_count = abi.make(abi.Uint64)
    partial_results = [
        abi.make(abi.Tuple2[abi.String, abi.Uint64]) for i in range(NUM_OPTIONS)
    ]
    results = abi.make(abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[NUM_OPTIONS]])  # type: ignore[valid-type]
    return Seq(
        question.set(App.globalGet(question_key)),
        can_resubmit.set(App.globalGet(resubmit_key)),
        is_open.set(App.globalGet(open_key)),
        *[
            Seq(
                option_name.set(App.globalGet(option_name_keys[i])),
                option_count.set(App.globalGet(option_count_keys[i])),
                partial_results[i].set(option_name, option_count),
            )
            for i in range(NUM_OPTIONS)
        ],
        results.set(partial_results),
        output.set(question, can_resubmit, is_open, results),
    )

contract/contract.py¶

In [24]:
show()
# This example is provided for informational purposes only and has not been audited for security.
import json
from typing import Literal
from pyteal import *


pragma(compiler_version="0.20.1")

on_delete = Seq(
    Assert(Txn.sender() == Global.creator_address()),
    InnerTxnBuilder.Execute(
        {
            TxnField.type_enum: TxnType.Payment,
            TxnField.close_remainder_to: Txn.sender(),
        }
    ),
)


router = Router(
    name="OpenPollingApp",
    descr="A polling application with no restrictions on who can participate.",
    bare_calls=BareCallActions(
        delete_application=OnCompleteAction.call_only(on_delete)
    ),
)

NUM_OPTIONS = 7

open_key = Bytes(b"open")
resubmit_key = Bytes(b"resubmit")
question_key = Bytes(b"question")
option_name_prefix = b"option_name_"
option_name_keys = [Bytes(option_name_prefix + bytes([i])) for i in range(NUM_OPTIONS)]
option_count_prefix = b"option_count_"
option_count_keys = [
    Bytes(option_count_prefix + bytes([i])) for i in range(NUM_OPTIONS)
]


@router.method(no_op=CallConfig.CREATE)
def create(
    question: abi.String, options: abi.StaticArray[abi.String, Literal[NUM_OPTIONS]], can_resubmit: abi.Bool  # type: ignore[valid-type]
) -> Expr:
    """Create a new polling application.

    Args:
        question: The question this poll is asking.
        options: A list of options for the poll. This list should not contain duplicate entries.
        can_resubmit: Whether this poll allows accounts to change their submissions or not.
    """
    name = abi.make(abi.String)
    return Seq(
        App.globalPut(open_key, Int(0)),
        App.globalPut(resubmit_key, can_resubmit.get()),
        App.globalPut(question_key, question.get()),
        *[
            Seq(
                name.set(options[i]),
                App.globalPut(option_name_keys[i], name.get()),
                App.globalPut(option_count_keys[i], Int(0)),
            )
            for i in range(NUM_OPTIONS)
        ],
    )


@router.method(name="open")
def open_poll() -> Expr:
    """Marks this poll as open.

    This will fail if the poll is already open.

    The poll must be open in order to receive user input.
    """
    return Seq(
        Assert(Not(App.globalGet(open_key))),
        App.globalPut(open_key, Int(1)),
    )


@router.method(name="close")
def close_poll() -> Expr:
    """Marks this poll as closed.

    This will fail if the poll is already closed.
    """
    return Seq(
        Assert(App.globalGet(open_key)),
        App.globalPut(open_key, Int(0)),
    )


@router.method
def submit(choice: abi.Uint8) -> Expr:
    """Submit a response to the poll.

    Submissions can only be received if the poll is open. If the poll is closed, this will fail.

    If a submission has already been made by the sender and the poll allows resubmissions, the
    sender's choice will be updated to the most recent submission. If the poll does not allow
    resubmissions, this action will fail.

    Args:
        choice: The choice made by the sender. This must be an index into the options for this poll.
    """
    new_choice_count_key = ScratchVar(TealType.bytes)
    old_choice_count_key = ScratchVar(TealType.bytes)
    return Seq(
        Assert(choice.get() < Int(NUM_OPTIONS)),
        new_choice_count_key.store(
            SetByte(option_count_keys[0], Int(len(option_count_prefix)), choice.get())
        ),
        sender_box := App.box_get(Txn.sender()),
        If(sender_box.hasValue()).Then(
            # the sender has already submitted a response, so it must be cleared
            Assert(App.globalGet(resubmit_key)),
            old_choice_count_key.store(
                SetByte(
                    option_count_keys[0],
                    Int(len(option_count_prefix)),
                    Btoi(sender_box.value()),
                )
            ),
            App.globalPut(
                old_choice_count_key.load(),
                App.globalGet(old_choice_count_key.load()) - Int(1),
            ),
        ),
        App.box_put(Txn.sender(), choice.encode()),
        App.globalPut(
            new_choice_count_key.load(),
            App.globalGet(new_choice_count_key.load()) + Int(1),
        ),
    )


class PollStatus(abi.NamedTuple):
    question: abi.Field[abi.String]
    can_resubmit: abi.Field[abi.Bool]
    is_open: abi.Field[abi.Bool]
    results: abi.Field[abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[NUM_OPTIONS]]]  # type: ignore[valid-type]


@router.method
def status(*, output: PollStatus) -> Expr:
    """Get the status of this poll.

    Returns:
        A tuple containing the following information, in order: the question is poll is asking,
        whether the poll allows resubmission, whether the poll is open, and an array of the poll's
        current results. This array contains one entry per option, and each entry is a tuple of that
        option's value and the number of accounts who have voted for it.
    """
    question = abi.make(abi.String)
    can_resubmit = abi.make(abi.Bool)
    is_open = abi.make(abi.Bool)
    option_name = abi.make(abi.String)
    option_count = abi.make(abi.Uint64)
    partial_results = [
        abi.make(abi.Tuple2[abi.String, abi.Uint64]) for i in range(NUM_OPTIONS)
    ]
    results = abi.make(abi.StaticArray[abi.Tuple2[abi.String, abi.Uint64], Literal[NUM_OPTIONS]])  # type: ignore[valid-type]
    return Seq(
        question.set(App.globalGet(question_key)),
        can_resubmit.set(App.globalGet(resubmit_key)),
        is_open.set(App.globalGet(open_key)),
        *[
            Seq(
                option_name.set(App.globalGet(option_name_keys[i])),
                option_count.set(App.globalGet(option_count_keys[i])),
                partial_results[i].set(option_name, option_count),
            )
            for i in range(NUM_OPTIONS)
        ],
        results.set(partial_results),
        output.set(question, can_resubmit, is_open, results),
    )
In [25]:
approval, clear, json_contract = router.compile_program(version=8, optimize=opts)

JSON Contract Stub - COMPLETE¶

In [26]:
# print(json.dumps(json_contract.dictify(), indent=2))
In [27]:
%%script false --no-raise-error # {
  "name": "OpenPollingApp",
  "methods": [
    {
      "name": "create",
      "args": [
        {
          "type": "string",
          "name": "question",
          "desc": "The question this poll is asking."
        },
        {
          "type": "string[7]",
          "name": "options",
          "desc": "A list of options for the poll. This list should not contain duplicate entries."
        },
        {
          "type": "bool",
          "name": "can_resubmit",
          "desc": "Whether this poll allows accounts to change their submissions or not."
        }
      ],
      "returns": {
        "type": "void"
      },
      "desc": "Create a new polling application."
    },
    {
      "name": "open",
      "args": [],
      "returns": {
        "type": "void"
      },
      "desc": "Marks this poll as open.\nThis will fail if the poll is already open.\nThe poll must be open in order to receive user input."
    },
    {
      "name": "close",
      "args": [],
      "returns": {
        "type": "void"
      },
      "desc": "Marks this poll as closed.\nThis will fail if the poll is already closed."
    },
    {
      "name": "submit",
      "args": [
        {
          "type": "uint8",
          "name": "choice",
          "desc": "The choice made by the sender. This must be an index into the options for this poll."
        }
      ],
      "returns": {
        "type": "void"
      },
      "desc": "Submit a response to the poll.\nSubmissions can only be received if the poll is open. If the poll is closed, this will fail.\nIf a submission has already been made by the sender and the poll allows resubmissions, the sender's choice will be updated to the most recent submission. If the poll does not allow resubmissions, this action will fail."
    },
    {
      "name": "status",
      "args": [],
      "returns": {
        "type": "(string,bool,bool,(string,uint64)[7])",
        "desc": "A tuple containing the following information, in order: the question is poll is asking, whether the poll allows resubmission, whether the poll is open, and an array of the poll's current results. This array contains one entry per option, and each entry is a tuple of that option's value and the number of accounts who have voted for it."
      },
      "desc": "Get the status of this poll."
    }
  ],
  "networks": {},
  "desc": "A polling application with no restrictions on who can participate."
}

see contract/contract.json¶

In [29]:
show()
{
  "name": "OpenPollingApp",
  "methods": [
    {
      "name": "create",
      "args": [
        {
          "type": "string",
          "name": "question",
          "desc": "The question this poll is asking."
        },
        {
          "type": "string[7]",
          "name": "options",
          "desc": "A list of options for the poll. This list should not contain duplicate entries."
        },
        {
          "type": "bool",
          "name": "can_resubmit",
          "desc": "Whether this poll allows accounts to change their submissions or not."
        }
      ],
      "returns": {
        "type": "void"
      },
      "desc": "Create a new polling application."
    },
    {
      "name": "open",
      "args": [],
      "returns": {
        "type": "void"
      },
      "desc": "Marks this poll as open.\nThis will fail if the poll is already open.\nThe poll must be open in order to receive user input."
    },
    {
      "name": "close",
      "args": [],
      "returns": {
        "type": "void"
      },
      "desc": "Marks this poll as closed.\nThis will fail if the poll is already closed."
    },
    {
      "name": "submit",
      "args": [
        {
          "type": "uint8",
          "name": "choice",
          "desc": "The choice made by the sender. This must be an index into the options for this poll."
        }
      ],
      "returns": {
        "type": "void"
      },
      "desc": "Submit a response to the poll.\nSubmissions can only be received if the poll is open. If the poll is closed, this will fail.\nIf a submission has already been made by the sender and the poll allows resubmissions, the sender's choice will be updated to the most recent submission. If the poll does not allow resubmissions, this action will fail."
    },
    {
      "name": "status",
      "args": [],
      "returns": {
        "type": "(string,bool,bool,(string,uint64)[7])",
        "desc": "A tuple containing the following information, in order: the question is poll is asking, whether the poll allows resubmission, whether the poll is open, and an array of the poll's current results. This array contains one entry per option, and each entry is a tuple of that option's value and the number of accounts who have voted for it."
      },
      "desc": "Get the status of this poll."
    }
  ],
  "networks": {},
  "desc": "A polling application with no restrictions on who can participate."
}
In [30]:
# print(approval)
In [31]:
%%script false --no-raise-error #pragma version 8
txn NumAppArgs
int 0
==
bnz main_l12
txna ApplicationArgs 0
method "create(string[3],bool)void"
==
bnz main_l11
txna ApplicationArgs 0
method "open()void"
==
bnz main_l10
txna ApplicationArgs 0
method "close()void"
==
bnz main_l9
txna ApplicationArgs 0
method "submit(uint8)void"
==
bnz main_l8
txna ApplicationArgs 0
method "status()(bool,bool,(string,uint64)[3])"
==
bnz main_l7
err
main_l7:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub status_4
store 2
byte 0x151f7c75
load 2
concat
log
int 1
return
main_l8:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
txna ApplicationArgs 1
int 0
getbyte
callsub submit_3
int 1
return
main_l9:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub close_2
int 1
return
main_l10:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub open_1
int 1
return
main_l11:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
==
&&
assert
txna ApplicationArgs 1
store 0
txna ApplicationArgs 2
int 0
int 8
*
getbit
store 1
load 0
load 1
callsub create_0
int 1
return
main_l12:
txn OnCompletion
int DeleteApplication
==
bnz main_l14
err
main_l14:
txn ApplicationID
int 0
!=
assert
txn Sender
global CreatorAddress
==
assert
int 1
return

// create
create_0:
store 20
store 19
byte 0x6f70656e
int 0
app_global_put
byte 0x72657375626d6974
load 20
app_global_put
load 19
load 19
int 2
int 0
*
extract_uint16
int 0
int 1
+
int 3
==
bnz create_0_l8
load 19
int 2
int 0
*
int 2
+
extract_uint16
create_0_l2:
substring3
store 21
byte 0x6f7074696f6e5f6e616d655f00
load 21
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f00
int 0
app_global_put
load 19
load 19
int 2
int 1
*
extract_uint16
int 1
int 1
+
int 3
==
bnz create_0_l7
load 19
int 2
int 1
*
int 2
+
extract_uint16
create_0_l4:
substring3
store 21
byte 0x6f7074696f6e5f6e616d655f01
load 21
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f01
int 0
app_global_put
load 19
load 19
int 2
int 2
*
extract_uint16
int 2
int 1
+
int 3
==
bnz create_0_l6
load 19
int 2
int 2
*
int 2
+
extract_uint16
b create_0_l9
create_0_l6:
load 19
len
b create_0_l9
create_0_l7:
load 19
len
b create_0_l4
create_0_l8:
load 19
len
b create_0_l2
create_0_l9:
substring3
store 21
byte 0x6f7074696f6e5f6e616d655f02
load 21
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f02
int 0
app_global_put
retsub

// open
open_1:
byte 0x6f70656e
app_global_get
!
assert
byte 0x6f70656e
int 1
app_global_put
retsub

// close
close_2:
byte 0x6f70656e
app_global_get
assert
byte 0x6f70656e
int 0
app_global_put
retsub

// submit
submit_3:
store 22
load 22
int 3
<
assert
byte 0x6f7074696f6e5f636f756e745f00
int 13
load 22
setbyte
store 23
txn Sender
box_get
store 26
store 25
load 26
bz submit_3_l2
byte 0x72657375626d6974
app_global_get
assert
byte 0x6f7074696f6e5f636f756e745f00
int 13
load 25
btoi
setbyte
store 24
load 24
load 24
app_global_get
int 1
-
app_global_put
submit_3_l2:
txn Sender
byte 0x00
int 0
load 22
setbyte
box_put
load 23
load 23
app_global_get
int 1
+
app_global_put
retsub

// status
status_4:
byte 0x72657375626d6974
app_global_get
!
!
store 3
byte 0x6f70656e
app_global_get
!
!
store 4
byte 0x6f7074696f6e5f6e616d655f00
app_global_get
store 5
load 5
len
itob
extract 6 0
load 5
concat
store 5
byte 0x6f7074696f6e5f636f756e745f00
app_global_get
store 6
load 5
store 11
int 10
itob
extract 6 0
load 6
itob
concat
load 11
concat
store 7
byte 0x6f7074696f6e5f6e616d655f01
app_global_get
store 5
load 5
len
itob
extract 6 0
load 5
concat
store 5
byte 0x6f7074696f6e5f636f756e745f01
app_global_get
store 6
load 5
store 12
int 10
itob
extract 6 0
load 6
itob
concat
load 12
concat
store 8
byte 0x6f7074696f6e5f6e616d655f02
app_global_get
store 5
load 5
len
itob
extract 6 0
load 5
concat
store 5
byte 0x6f7074696f6e5f636f756e745f02
app_global_get
store 6
load 5
store 13
int 10
itob
extract 6 0
load 6
itob
concat
load 13
concat
store 9
load 7
store 17
load 17
store 16
int 6
store 14
load 14
load 17
len
+
store 15
load 15
int 65536
<
assert
load 14
itob
extract 6 0
load 8
store 17
load 16
load 17
concat
store 16
load 15
store 14
load 14
load 17
len
+
store 15
load 15
int 65536
<
assert
load 14
itob
extract 6 0
concat
load 9
store 17
load 16
load 17
concat
store 16
load 15
store 14
load 14
itob
extract 6 0
concat
load 16
concat
store 10
byte 0x00
int 0
load 3
setbit
int 1
load 4
setbit
load 10
store 18
int 3
itob
extract 6 0
concat
load 18
concat
retsub

see contract/approval.teal¶

In [33]:
show()
#pragma version 8
txn NumAppArgs
int 0
==
bnz main_l12
txna ApplicationArgs 0
method "create(string,string[7],bool)void"
==
bnz main_l11
txna ApplicationArgs 0
method "open()void"
==
bnz main_l10
txna ApplicationArgs 0
method "close()void"
==
bnz main_l9
txna ApplicationArgs 0
method "submit(uint8)void"
==
bnz main_l8
txna ApplicationArgs 0
method "status()(string,bool,bool,(string,uint64)[7])"
==
bnz main_l7
err
main_l7:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub status_4
store 3
byte 0x151f7c75
load 3
concat
log
int 1
return
main_l8:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
txna ApplicationArgs 1
int 0
getbyte
callsub submit_3
int 1
return
main_l9:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub close_2
int 1
return
main_l10:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
!=
&&
assert
callsub open_1
int 1
return
main_l11:
txn OnCompletion
int NoOp
==
txn ApplicationID
int 0
==
&&
assert
txna ApplicationArgs 1
store 0
txna ApplicationArgs 2
store 1
txna ApplicationArgs 3
int 0
int 8
*
getbit
store 2
load 0
load 1
load 2
callsub create_0
int 1
return
main_l12:
txn OnCompletion
int DeleteApplication
==
bnz main_l14
err
main_l14:
txn ApplicationID
int 0
!=
assert
txn Sender
global CreatorAddress
==
assert
itxn_begin
int pay
itxn_field TypeEnum
txn Sender
itxn_field CloseRemainderTo
itxn_submit
int 1
return

// create
create_0:
store 34
store 33
store 32
byte 0x6f70656e
int 0
app_global_put
byte 0x72657375626d6974
load 34
app_global_put
byte 0x7175657374696f6e
load 32
extract 2 0
app_global_put
load 33
load 33
int 2
int 0
*
extract_uint16
int 0
int 1
+
int 7
==
bnz create_0_l20
load 33
int 2
int 0
*
int 2
+
extract_uint16
create_0_l2:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f00
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f00
int 0
app_global_put
load 33
load 33
int 2
int 1
*
extract_uint16
int 1
int 1
+
int 7
==
bnz create_0_l19
load 33
int 2
int 1
*
int 2
+
extract_uint16
create_0_l4:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f01
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f01
int 0
app_global_put
load 33
load 33
int 2
int 2
*
extract_uint16
int 2
int 1
+
int 7
==
bnz create_0_l18
load 33
int 2
int 2
*
int 2
+
extract_uint16
create_0_l6:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f02
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f02
int 0
app_global_put
load 33
load 33
int 2
int 3
*
extract_uint16
int 3
int 1
+
int 7
==
bnz create_0_l17
load 33
int 2
int 3
*
int 2
+
extract_uint16
create_0_l8:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f03
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f03
int 0
app_global_put
load 33
load 33
int 2
int 4
*
extract_uint16
int 4
int 1
+
int 7
==
bnz create_0_l16
load 33
int 2
int 4
*
int 2
+
extract_uint16
create_0_l10:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f04
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f04
int 0
app_global_put
load 33
load 33
int 2
int 5
*
extract_uint16
int 5
int 1
+
int 7
==
bnz create_0_l15
load 33
int 2
int 5
*
int 2
+
extract_uint16
create_0_l12:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f05
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f05
int 0
app_global_put
load 33
load 33
int 2
int 6
*
extract_uint16
int 6
int 1
+
int 7
==
bnz create_0_l14
load 33
int 2
int 6
*
int 2
+
extract_uint16
b create_0_l21
create_0_l14:
load 33
len
b create_0_l21
create_0_l15:
load 33
len
b create_0_l12
create_0_l16:
load 33
len
b create_0_l10
create_0_l17:
load 33
len
b create_0_l8
create_0_l18:
load 33
len
b create_0_l6
create_0_l19:
load 33
len
b create_0_l4
create_0_l20:
load 33
len
b create_0_l2
create_0_l21:
substring3
store 35
byte 0x6f7074696f6e5f6e616d655f06
load 35
extract 2 0
app_global_put
byte 0x6f7074696f6e5f636f756e745f06
int 0
app_global_put
retsub

// open
open_1:
byte 0x6f70656e
app_global_get
!
assert
byte 0x6f70656e
int 1
app_global_put
retsub

// close
close_2:
byte 0x6f70656e
app_global_get
assert
byte 0x6f70656e
int 0
app_global_put
retsub

// submit
submit_3:
store 36
load 36
int 7
<
assert
byte 0x6f7074696f6e5f636f756e745f00
int 13
load 36
setbyte
store 37
txn Sender
box_get
store 40
store 39
load 40
bz submit_3_l2
byte 0x72657375626d6974
app_global_get
assert
byte 0x6f7074696f6e5f636f756e745f00
int 13
load 39
btoi
setbyte
store 38
load 38
load 38
app_global_get
int 1
-
app_global_put
submit_3_l2:
txn Sender
byte 0x00
int 0
load 36
setbyte
box_put
load 37
load 37
app_global_get
int 1
+
app_global_put
retsub

// status
status_4:
byte 0x7175657374696f6e
app_global_get
store 4
load 4
len
itob